import { any } from "../core/logic";
import { textureLoader } from "../assets/loader/TextureLoader";
import { Texture as BaseTexture } from "../texture/Texture";
import { Texture } from "../gpu/types/Texture";
import { Sampler } from "../gpu/types/Sampler";
import { Buffer } from "../gpu/types/Buffer";
import { ParamManager } from "../gpu/ParamManager";
import { Engine } from "../Engine";
import { isBlank, getSide, isTypedArray } from "../core/utils";
/**
 * 材质类。
 * @class
 * @memberof w.material
 */
class Material extends ParamManager {
  /**
   * 创建材质
   * @constructor
   * @param {*} opts
   * @param {Number} opts.opacity 【可选,缺省:1.0】材质的透明度。
   * @param {Boolean} opts.transparent 【可选,缺省:false】材质是否是透明材质。
   * @param {Boolean} opts.vertexColors 【可选,缺省:[]】顶点颜色。
   * @param {Boolean} opts.toneMapping 【可选,缺省:"NoToneMapping"】toneMapping颜色映射方式。
   * @param {Object} opts.message 【可选,缺省:{}】材质携带的信息。
   * @param {String} opts.side 【可选,缺省:"front"】绘制的面，可以是前面绘制"front"，背面绘制:"back"，双面绘制:"none"。
   * @param {Number} opts.alphaCutoff 【可选,缺省:0,0】透明度剔除。
   * @param {String} opts.blending 【可选,缺省:undefine】混合模式，可选:"CustomBlending"|"NormalBlending"|"AdditiveBlending"|"SubtractiveBlending"|"MultiplyBlending"。
   * @param {Boolean} opts.depthTest 【可选,缺省:true】深度测试。
   * @param {String} opts.depthFunc 【可选,缺省:"less"】深度测试函数。
   * @param {Boolean} opts.depthWrite 【可选,缺省:true】深度写入。
   * @param {Boolean} opts.defuseLight 【可选,缺省:false】拒绝光照。
  */
  constructor(opts = {}, share) {
    super(opts, share);
    this.opacity = any(opts.opacity, 1.0);
    this.transparent = any(opts.transparent, false);
    this.vertexColors = any(opts.vertexColors, []);
    this.toneMapping = any(opts.toneMapping, 'NoToneMapping');
    this.toneMappingExposure = any(opts.toneMappingExposure, 1);
    this.message = any(opts.message, {});
    this.side = any(opts.side, "front");
    this.alphaCutoff = opts.alphaCutoff
    this.alphaSrcFactor = opts.alphaSrcFactor;
    this.alphaDstFactor = opts.alphaDstFactor;
    this.alphaOperation = opts.alphaOperation;
    this.colorSrcFactor = opts.colorSrcFactor;
    this.colorDstFactor = opts.colorDstFactor;
    this.colorOperation = opts.colorOperation;
    this.blending = opts.blending
    this.depthTest = any(opts.depthTest, true);
    this.depthFunc = any(opts.depthFunc, 'less');
    this.depthWrite = any(opts.depthWrite, true);
    this.stencilWrite = any(opts.stencilWrite, false);
    this.stencilWriteMask = opts.stencilWriteMask;
    this.stencilFunc = opts.stencilFunc;
    this.stencilRef = opts.stencilRef;
    this.stencilFuncMask = opts.stencilFuncMask;
    this.stencilFail = opts.stencilFail;
    this.stencilZFail = opts.stencilZFail;
    this.stencilZPass = opts.stencilZPass;
    this.defuseLight = any(opts.defuseLight, false);
    this.receiveShadow = any(opts.receiveShadow, true);
    this._hash = '';
  }
  get hash() {
    return this._hash;
  }
  set opacity(v) {
    this._opacity = v;
    this.addParam("material")
    this.update();
    return this;
  }
  get opacity() {
    return this._opacity;
  }
  set transparent(v) {
    if (isBlank(v)) return
    this._transparent = v;
    this.addConfigParam("transparent", v);
    this.update();
    return this;
  }
  get transparent() {
    return this._transparent;
  }
  set vertexColors(v) {
    if (isBlank(v)) return
    this._vertexColors = v;
    this.update();
    return this;
  }
  get vertexColors() {
    return this._vertexColors;
  }
  set toneMapping(v) {
    if (isBlank(v)) return
    this._toneMapping = v;
    this.addConfigParam("toneMapping", v);
    if (this.toneMapping !== 'NoToneMapping') {
      this.addParam("toneMappingExposure")
    } else {
      this.removeParam("toneMappingExposure")
    }
    this.update();
    return this;
  }
  get toneMapping() {
    return this._toneMapping;
  }
  set toneMappingExposure(v) {
    if (isBlank(v)) return
    this._toneMappingExposure = v;
    this.update();
    return this;
  }
  get toneMappingExposure() {
    return this._toneMappingExposure;
  }
  set side(v) {
    if (isBlank(v)) return
    this._side = v;
    let sideData = getSide(v)
    this.addConfigParam("primitive", {
      topology: 'triangle-list',
      frontFace: 'ccw',
      cullMode: sideData
    });
    this.update();
    return this;
  }
  get side() {
    return this._side;
  }
  set alphaCutoff(v) {
    if (isBlank(v)) return
    this._alphaCutoff = v;
    this.addParam("alphaCutoff")
    this.update();
    return this;
  }
  get alphaCutoff() {
    return this._alphaCutoff;
  }
  set alphaSrcFactor(v) {
    if (isBlank(v)) return
    this._alphaSrcFactor = v;
    this.addConfigParam("blendMode", {
      alpha: {
        operation: this.alphaOperation,
        srcFactor: this.alphaSrcFactor,
        dstFactor: this.alphaDstFactor
      },
      color: {
        operation: this.colorOperation,
        srcFactor: this.colorSrcFactor,
        dstFactor: this.colorDstFactor
      }
    });
    this.update();
    return this;
  }
  get alphaSrcFactor() {
    return this._alphaSrcFactor;
  }
  set alphaDstFactor(v) {
    if (isBlank(v)) return
    this._alphaDstFactor = v;
    this.addConfigParam("blendMode", {
      alpha: {
        operation: this.alphaOperation,
        srcFactor: this.alphaSrcFactor,
        dstFactor: this.alphaDstFactor
      },
      color: {
        operation: this.colorOperation,
        srcFactor: this.colorSrcFactor,
        dstFactor: this.colorDstFactor
      }
    });
    this.update();
    return this;
  }
  get alphaDstFactor() {
    return this._alphaDstFactor;
  }
  set alphaOperation(v) {
    if (isBlank(v)) return
    this._alphaOperation = v;
    this.addConfigParam("blendMode", {
      alpha: {
        operation: this.alphaOperation,
        srcFactor: this.alphaSrcFactor,
        dstFactor: this.alphaDstFactor
      },
      color: {
        operation: this.colorOperation,
        srcFactor: this.colorSrcFactor,
        dstFactor: this.colorDstFactor
      }
    });
    this.update();
    return this;
  }
  get alphaOperation() {
    return this._alphaOperation;
  }
  set colorSrcFactor(v) {
    if (isBlank(v)) return
    this._colorSrcFactor = v;
    this.addConfigParam("blendMode", {
      alpha: {
        operation: this.alphaOperation,
        srcFactor: this.alphaSrcFactor,
        dstFactor: this.alphaDstFactor
      },
      color: {
        operation: this.colorOperation,
        srcFactor: this.colorSrcFactor,
        dstFactor: this.colorDstFactor
      }
    });
    this.update();
    return this;
  }
  get colorSrcFactor() {
    return this._colorSrcFactor;
  }
  set colorDstFactor(v) {
    if (isBlank(v)) return
    this._colorDstFactor = v;
    this.addConfigParam("blendMode", {
      alpha: {
        operation: this.alphaOperation,
        srcFactor: this.alphaSrcFactor,
        dstFactor: this.alphaDstFactor
      },
      color: {
        operation: this.colorOperation,
        srcFactor: this.colorSrcFactor,
        dstFactor: this.colorDstFactor
      }
    });
    this.update();
    return this;
  }
  get colorDstFactor() {
    return this._colorDstFactor;
  }
  set colorOperation(v) {
    if (isBlank(v)) return
    this._colorOperation = v;
    this.addConfigParam("blendMode", {
      alpha: {
        operation: this.alphaOperation,
        srcFactor: this.alphaSrcFactor,
        dstFactor: this.alphaDstFactor
      },
      color: {
        operation: this.colorOperation,
        srcFactor: this.colorSrcFactor,
        dstFactor: this.colorDstFactor
      }
    });
    this.update();
    return this;
  }
  get colorOperation() {
    return this._colorOperation;
  }
  set blending(v) {
    if (isBlank(v)) return
    this._blending = v;
    this.addConfigParam("blendMode", this.getBlendMode(v));
    this.update();
    return this;
  }
  get blending() {
    return this._blending;
  }
  set depthTest(v) {
    if (isBlank(v)) return
    this._depthTest = v;
    this.update();
    return this;
  }
  get depthTest() {
    return this._depthTest;
  }
  set depthFunc(v) {
    if (isBlank(v)) return
    this._depthFunc = v;
    this.update();
    return this;
  }
  get depthFunc() {
    return this._depthFunc;
  }
  set depthWrite(v) {
    if (isBlank(v)) return
    this._depthWrite = v;
    this.update();
    return this;
  }
  get depthWrite() {
    return this._depthWrite;
  }
  set stencilWriteMask(v) {
    if (isBlank(v)) return
    this._stencilWriteMask = v;
    this.update();
    return this;
  }
  get stencilWriteMask() {
    return this._stencilWriteMask;
  }
  set stencilFunc(v) {
    if (isBlank(v)) return
    this._stencilFunc = v;
    this.update();
    return this;
  }
  get stencilFunc() {
    return this._stencilFunc;
  }
  set stencilRef(v) {
    if (isBlank(v)) return
    this._stencilRef = v;
    this.update();
    return this;
  }
  get stencilRef() {
    return this._stencilRef;
  }
  set stencilFuncMask(v) {
    if (isBlank(v)) return
    this._stencilFuncMask = v;
    this.update();
    return this;
  }
  get stencilFuncMask() {
    return this._stencilFuncMask;
  }
  set stencilFail(v) {
    if (isBlank(v)) return
    this._stencilFail = v;
    this.update();
    return this;
  }
  get stencilFail() {
    return this._stencilFail;
  }
  set stencilZFail(v) {
    if (isBlank(v)) return
    this._stencilZFail = v;
    this.update();
    return this;
  }
  get stencilZFail() {
    return this._stencilZFail;
  }
  set stencilZPass(v) {
    if (isBlank(v)) return
    this._stencilZPass = v;
    this.update();
    return this;
  }
  get stencilZPass() {
    return this._stencilZPass;
  }
  set stencilWrite(v) {
    if (isBlank(v)) return
    this._stencilWrite = v;
    this.addConfigParam("depthStencil", this.getDepthStencil(v));
    this.update();
    return this;
  }
  get stencilWrite() {
    return this._stencilWrite;
  }
  set defuseLight(v) {
    if (isBlank(v)) return
    this._defuseLight = v;
    this.addParam("material")
    this.update();
    return this;
  }
  get defuseLight() {
    return this._defuseLight;
  }
  set receiveShadow(v) {
    if (isBlank(v)) return
    this._receiveShadow = v;
    this.addConfigParam("receiveShadow",v)
    this.update();
    return this;
  }
  get receiveShadow() {
    return this._receiveShadow;
  }

  getBlendMode(blending) {
    let blendMode = {
      alpha: {
        operation: 'add',
        srcFactor: 'one',
        dstFactor: 'one'
      },
      color: {
        operation: 'add',
        srcFactor: 'src-alpha',
        dstFactor: 'one-minus-src-alpha'
      }
    };
    switch (blending) {
      case 'CustomBlending':
        blendMode.alpha.srcFactor = this.blendSrcAlpha;
        blendMode.alpha.dstFactor = this.blendDstAlpha;
        blendMode.alpha.operation = this.blendEquation;
        blendMode.color.srcFactor = this.blendSrc;
        blendMode.color.dstFactor = this.blendDst;
        blendMode.color.operation = this.blendEquation;
        break;
      case 'NormalBlending':
        blendMode.alpha.srcFactor = 'one';
        blendMode.alpha.dstFactor = 'one';
        blendMode.alpha.operation = 'add';
        blendMode.color.srcFactor = 'src-alpha';
        blendMode.color.dstFactor = 'one-minus-src-alpha';
        blendMode.color.operation = 'add';

        break;

      case 'AdditiveBlending':
        blendMode.alpha.srcFactor = 'zero';
        blendMode.alpha.dstFactor = 'one';
        blendMode.alpha.operation = 'add';
        blendMode.color.srcFactor = 'one';
        blendMode.color.dstFactor = 'one';
        blendMode.color.operation = 'add';
        break;

      case 'SubtractiveBlending':
        blendMode.alpha.srcFactor = 'one-minus-src-color';
        blendMode.alpha.dstFactor = 'one-minus-src-alpha';
        blendMode.alpha.operation = 'add';
        blendMode.color.srcFactor = 'zero';
        blendMode.color.dstFactor = 'zero';
        blendMode.color.operation = 'add';
        break;

      case 'MultiplyBlending':
        blendMode.alpha.srcFactor = 'zero';
        blendMode.alpha.dstFactor = 'src-alpha';
        blendMode.alpha.operation = 'add';
        blendMode.color.srcFactor = 'zero';
        blendMode.color.dstFactor = 'src-color';
        blendMode.color.operation = 'add';

        break;

      default:
        throw new Error('不支持的blend模式');
    }
    return blendMode;
  }
  getDepthStencil(stencilWrite) {
    let depthStencil = {
      format: this.depthTest && stencilWrite ? 'depth24plus-stencil8' : 'depth24plus',
      depthWriteEnabled: this.depthWrite,
      depthCompare: this.depthFunc,
    }
    if (stencilWrite) {
      depthStencil.stencilFront = {
        compare: this.stencilFunc,
        failOp: this.stencilFail,
        depthFailOp: this.stencilZFail,
        passOp: this.stencilZPass
      };
      depthStencil.stencilBack = {};
      depthStencil.stencilReadMask = this.stencilFuncMask;
      depthStencil.stencilWriteMask = this.stencilWriteMask;
    }
    return depthStencil
  }
  bufferToGPU(data, name, visibility = GPUShaderStage.FRAGMENT) {
    let parameter = this.getParam(name);
    if (parameter) {
      if (!parameter.state) {
        parameter.buffer(data);
      }
    } else {
      parameter = new Buffer({
        name: name,
        usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
        visibility,
        type: "uniform",
        size: data.byteLength,
        data
      })
      this.addParam(name, parameter);
    }
    parameter.state = true;
  }
  textureToGPU(data, name, visibility = GPUShaderStage.FRAGMENT) {
    let { texture, viewDimension, format, usage, sampler, multisampled, sampleType, uvIndex, uvTransform } = data;
    let mapParamId = name;
    let mapParamName = name;
    let samplerParamId = name + 'Sampler';
    let samplerParamName = name + 'Sampler';
    let uvTransformParamId = name + 'UVTransform';
    let uvTransformParamName = name + 'UVTransform';
    //map
    let mapGPUParameter = this.getParam(mapParamId);
    if (mapGPUParameter) {
      if (!mapGPUParameter.state) {
        mapGPUParameter.buffer(texture)
      }
    } else {
      mapGPUParameter = new Texture({
        name: mapParamName,
        id:data?.id,
        usage,
        visibility,
        sampleType,
        format,
        dimension: viewDimension,
        multisampled,
        data: texture
      })
      this.addParam(mapParamId, mapGPUParameter);
    }
    mapGPUParameter.state = true;

    //sampler
    let samplerGPUParameter = this.getParam(samplerParamId);
    if (samplerGPUParameter) {
      if (!samplerGPUParameter.state) {
        samplerGPUParameter.buffer(sampler)
      }
    } else {
      samplerGPUParameter = new Sampler({
        name: samplerParamName,
        visibility: GPUShaderStage.FRAGMENT,
        type: "filtering",
        data: sampler
      })
      this.addParam(samplerParamId, samplerGPUParameter);
    }
    samplerGPUParameter.state = true;
    //uvTransform
    if (uvTransform) {
      let UVTransformGPUParameter = this.getParam(uvTransformParamId);
      let uvTransformData = new Float32Array(16);
      uvTransformData.set(uvTransform);
      if (UVTransformGPUParameter) {
        if (!UVTransformGPUParameter.state) {
          UVTransformGPUParameter.buffer(uvTransformData)
        }

      } else {
        UVTransformGPUParameter = new Buffer({
          name: uvTransformParamName,
          usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
          visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
          type: "uniform",
          size: 64,
          data: uvTransformData
        })
        this.addParam(uvTransformParamId, UVTransformGPUParameter);
      }
      UVTransformGPUParameter.state = true;
    }
    this.setState(true, name + "Param");
  }
  addMapParam(name) {
    this.addParam(name);
    this.addParam(name + 'Sampler');
    if (this[`${name}`]?.uvTransform) {
      this.addParam(name + 'UVTransform');
    }
    let uvIndex = this[`${name}`].uvIndex;
    if (uvIndex) {
      this.addConfigParam(name + 'UVIndex', uvIndex)
    }
  }
  updateHash() {
    let hash = '';
    this.getAll().forEach(([paramsId, param]) => {
      if (param && param.isConfig) {
        hash = hash + paramsId + (typeof param.value === 'object' ? JSON.stringify(param.value) : param.value);
      } else {
        hash = hash + paramsId;
      }
    })
    this._hash = hash;
  }
  async registerTexture(value, name, callback) {
    //构建一个纹理对象
    let data;
    let opts;
    if (typeof value === 'object' && value.constructor.name !== 'ImageBitmap' && !Array.isArray(value) && !(value instanceof BaseTexture)) {
      opts = { ...value };
      value = value.url;
      delete opts.url;
    }
    let id;
    if (Array.isArray(value)) {
      if (value[0].constructor.name === 'String' || value[0].constructor.name === 'ImageBitmap') {
        id = this.id + name;
        if (value.length === 6) {
          await textureLoader.loadTextureCube(value, { id, ...opts })
        } else {
          await textureLoader.loadTexture2DArray(value, { id, ...opts })
        }
      } else if (value[0] instanceof Object) {
        id = this.id + name;
        data= await textureLoader.loadTextureCompressedArrayBuffer(value, { id, ...opts })
      }
    } else if (value.constructor.name === "String" || value.constructor.name === "ImageBitmap") {
      id = value.constructor.name === "String" ? value : value.id + name;
      data = await textureLoader.loadTexture2D(value, { id, ...opts });
    } else if (value instanceof BaseTexture) {
      id = value.id;
      data=value
    } else if (value.constructor.name === "GPUTexture") {
      id = this.id + name;
      this.textureToGPU(BaseTexture({
        ...opts,
        texture: value,
      }).toGPU(), name)

    } else if (isTypedArray(value)) {
      id = value.id + name;
     data= await textureLoader.loadTexture2DType(value, { id, ...opts });
    }
    callback(data);
  }
  toGPU() {
    if (this.has("alphaCutoff")) {
      this.bufferToGPU(new Float32Array([this.alphaCutoff]), 'alphaCutoff', GPUShaderStage.FRAGMENT);
    }
  }
  destroy() {
    if (this.watchers.length <= 0) {
      this.removeAllParam();
      return true;
    }
    return false;
  }
}
export { Material };
